package org.atlantis.ymer.server.transport;

import org.atlantis.ymer.DataForwarder;
import org.atlantis.ymer.WebSession;
import org.atlantis.ymer.command.ConnectCommand;
import org.atlantis.ymer.command.DisconnectCommand;
import org.atlantis.ymer.command.PongCommand;
import org.atlantis.ymer.command.PubCommand;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;

public class WebSocketTransport extends AbstractTransport {

	private static final String WEBSOCKET_PATH = "/ymer/websocket";

	@Override
	public void handle(ChannelHandlerContext ctx, Object data) {
		if (data instanceof HttpRequest) { // connect handshake
			connect(ctx, (HttpRequest) data);
			return;
		}
		String webSessionId = (String) ctx.getAttachment();
		WebSession webSession = tryGetWebSession(webSessionId);
		if (webSession == null) {
			ctx.getChannel().write(new CloseWebSocketFrame()).addListener(ChannelFutureListener.CLOSE);
			return;
		}
		if (data instanceof CloseWebSocketFrame) {
			webSession.onData(DisconnectCommand.instance);
		} else if (data instanceof PongWebSocketFrame) {
			webSession.onData(PongCommand.instance);
		} else if (data instanceof TextWebSocketFrame) {
			passContentToWebSession(((TextWebSocketFrame) data).getText(), webSession);
		}
	}

	private void connect(ChannelHandlerContext ctx, HttpRequest request) {
		Channel channel = ctx.getChannel();
		WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(getWebSocketLocation(request),
				null, false);
		WebSocketServerHandshaker handshaker = wsFactory.newHandshaker(request);
		if (handshaker == null) {
			wsFactory.sendUnsupportedWebSocketVersionResponse(null);
			return;
		}
		handshaker.handshake(channel, request);
		final WebSession webSession = createNewWebSession();
		ctx.setAttachment(webSession.getId());

		DataForwarder dataForwarder = new WebSocketDataForwarder(ctx, webSession);
		webSession.onData(new ConnectCommand(dataForwarder));
	}

	private String getWebSocketLocation(HttpRequest request) {
		return "ws://" + request.getHeader(HttpHeaders.Names.HOST) + WEBSOCKET_PATH;
	}

	private class WebSocketDataForwarder extends AbstractDataForwarder {

		WebSocketDataForwarder(ChannelHandlerContext ctx, WebSession webSession) {
			super(ctx, webSession, true);
		}

		@Override
		public void sendConnect() {
			Channel channel = getChannel();
			if (channel == null) return;
			channel.write(new TextWebSocketFrame(new ConnectCommand(webSession.getId()).getContent()));
		}

		@Override
		public void sendPing() {
			Channel channel = getChannel();
			if (channel == null) return;
			channel.write(new PingWebSocketFrame());
		}

		@Override
		public void sendMessage(PubCommand command) {
			Channel channel = getChannel();
			if (channel == null) return;
			channel.write(new TextWebSocketFrame(command.getContent()));
		}

		@Override
		public void sendDisconnect(DisconnectCommand command) {
			Channel channel = getChannel();
			if (channel == null) return;
			channel.write(new CloseWebSocketFrame()).addListener(ChannelFutureListener.CLOSE);
		}
	}
}
